#include <buola/widgets/cdirtree.h>

#include <buola/app/ceventloop.h>
#include <buola/algorithm/split.h>
#include <buola/io/cfolder.h>
#include <buola/gui/cpropertyfield.h>
#include <buola/gui/ctoolkit.h>
#include <buola/gui/cclipboard.h>
#include <buola/io/cfilefilter.h>
#include <buola/gui/cmenu.h>
#include <buola/gui/cconfig.h>

#include <thread>
#include <buola/os/cmimetype.h>

#define ID_FIELD_URI        1
#define ID_FIELD_INTERFACE  2

#define DIRTREE_FILLTREE    1
#define DIRTREE_EXPANDED    2
#define DIRTREE_SELECTDIR   3

namespace buola { namespace gui {

CDirTree::CDirTree()
{
    mNumNodes=0;
    SetBackColor(img::CColor(0.835,0.867,0.902));

    AddField(new CPropertyField(L"uri",L"full url,",WI18N("Full URI"),nullptr,CPropertyField::PLF_HIDDEN),ID_FIELD_URI);
    AddField(new CPropertyField(L"pointer",L"Interface",WI18N("Interface"),nullptr,CPropertyField::PLF_HIDDEN),ID_FIELD_INTERFACE);

    thread(std::bind(&CDirTree::OnActionsThread,this)).detach();

    FillTree();
}

CDirTree::~CDirTree()
{
}

io::CURI CDirTree::GetURI(SNode *pNode)
{
    return pNode->Value(ID_FIELD_URI).Get<io::CURI>();
}

io::CFile CDirTree::GetFile(SNode *pNode)
{
    return pNode->Value(ID_FIELD_INTERFACE).Get<io::CFile>();
}

void CDirTree::SelectDir(const io::CURI &pDir)
{
    AddAction(DIRTREE_SELECTDIR,nullptr,pDir);
}

void CDirTree::SetFilter(io::PFileFilter pFilter)
{
    mFilter=pFilter;

    FillTree();
}

void CDirTree::SetURI(const io::CURI &pURI)
{
    mURI=pURI;

    FillTree();
}

void CDirTree::AddDir(SNode *pUnder,const io::CFolder &pFolder,bool pMakeSub)
{
    get_main_event_loop().GetMutex().lock();
    SetAutoUpdate(false);
    get_main_event_loop().GetMutex().unlock();

    try
    {
        std::vector<io::CFile> lFiles;

        lFiles=pFolder.GetChildren(mFilter?mFilter:nullptr);
        std::sort(lFiles.begin(),lFiles.end(),io::folder_first_order()>>io::file_name_order());

        for(int i=0,r=0;i<lFiles.size();i++,r++)
        {
            get_main_event_loop().GetMutex().lock();
            ///\todo
            //SNode *lNode=AddNode(pUnder,lInterface->GetTitle(),
            //                            lInterface->GetIcon());
            os::CMIMEType *lType=os::CMIMEType::Get(os::CMIMEType::Guess(lFiles[i]));
            SNode *lNode=AddNode(pUnder,utf32(lFiles[i].URI().Name()),
                                 CIcon::Get(lType->GetIcon(gConfig.mIcons.GetTheme())));

            lNode->Value(ID_FIELD_URI).Set<io::CURI>(lFiles[i].URI());
            lNode->Value(ID_FIELD_INTERFACE).Set<io::CFile>(lFiles[i]);
            get_main_event_loop().GetMutex().unlock();

            if(lFiles[i].IsFolder())
            {
                io::CFolder lFolder(lFiles[i].URI()); ///\todo fix this
                if(pMakeSub)
                {
                    AddDir(lNode,lFolder,false);
                }
                else
                {
                    try
                    {
///\todo                        lNode->mExpandable=lFolder.HasAnyFolderChild();
                        lNode->mExpandable=lFolder.HasChildren();
                    }
                    catch(XPermission&)
                    {
                        lNode->mExpandable=false;
                    }
                }
            }

            if(r>20)
            {
                get_main_event_loop().GetMutex().lock();
                Update();
                get_main_event_loop().GetMutex().unlock();
                r=0;
            }
        }
    }
    catch(XPermission&)
    {
        msg_error() << "Permission problem adding dir\n";
    }

    get_main_event_loop().GetMutex().lock();
    Update();
    SetAutoUpdate(true);
    get_main_event_loop().GetMutex().unlock();
}

void CDirTree::FillTree()
{
    AddAction(DIRTREE_FILLTREE);
}

void CDirTree::AddAction(int pCode,SNode *pNode,const io::CURI &pURI)
{
    CDirTreeAction *lAction=new CDirTreeAction;
    lAction->mCode=pCode;
    lAction->mNode=pNode;
    lAction->mURI=pURI;
    mPendingActions.Add(lAction);
}

void CDirTree::OnFillMenu(PMenu pMenu,bool pFile)
{
    if(pFile)
    {
        ///\todo
        /*
        mInterface=GetInterface(GetSelNode());
        mInterface->eMustUpdate.Connect(&CDirTree::FillTree,this);
        mInterface->eIntAction.Connect(&CDirTree::OnIntAction,this,_1,mInterface.get());
        mInterface->FillMenu(pMenu);
        */
    }
}

void CDirTree::OnNodeExpandedFirstTime(SNode *pNode)
{
    AddAction(DIRTREE_EXPANDED,pNode);
}

void CDirTree::OnNodeRemoved(SNode *pNode)
{
    CTreeBox::OnNodeRemoved(pNode);

    io::CFile lFile=GetFile(pNode);
///\todo     if(lInterface==mInterface) mInterface=nullptr;
}

void CDirTree::DoSelectDir(const io::CURI &pURI)
{
    io::CURI lURI=pURI;
    lURI.Normalize();
    std::wstring lPath=utf32(lURI.Path());

    auto lTokenizer=split_begin(lPath,fn::equal_to('/'));

    for(int i=0;i<mTWN.size();i++)
    {
        ++lTokenizer;       //Removes leading '/'

        SNode *lNode=mTWN[i];

        bool lFound=true;

        for(;lTokenizer!=split_end();++lTokenizer)
        {
            std::wstring lToken(lTokenizer->begin(),lTokenizer->end());
            if(!lNode->mExpanded)
            {
                get_main_event_loop().GetMutex().lock();
                lNode->Expand();
                get_main_event_loop().GetMutex().unlock();
                AddAction(DIRTREE_SELECTDIR,nullptr,pURI);
                return;
            }

            lFound=false;
            for(int j=0;j<lNode->mChildren.size();j++)
            {
                if(lNode->mChildren[j]->mText==lToken)
                {
                    lNode=lNode->mChildren[j];
                    lFound=true;
                    break;
                }
            }

            if(!lFound) break;
        }
        if(lFound)
        {
            get_main_event_loop().GetMutex().lock();
            SelectNode(lNode);
            EnsureVisible(lNode,true);
            get_main_event_loop().GetMutex().unlock();
            return;
        }
    }
}

void CDirTree::DoNodeExpandedFirstTime(SNode *pNode)
{
    AddDir(pNode,io::CFolder(pNode->Value(ID_FIELD_URI).Get<io::CURI>()),false);
}

void CDirTree::DoFillTree()
{
    msg_info() << "filling tree " << (void*)this << "\n";
    io::CFile lEntry(mURI);

    msg_info() << "before locking\n";
    get_main_event_loop().GetMutex().lock();
    RemoveAllNodes();
    msg_info() << "after locking\n";
    SetAutoUpdate(false);
    SNode *lNode=AddNode(nullptr,mURI.ToWString(),CIcon::Get("folder-violet"));

    lNode->Value(ID_FIELD_URI).Set<io::CURI>(lEntry.URI());
    lNode->Value(ID_FIELD_INTERFACE).Set<io::CFile>(lEntry);
    if(lEntry.IsFolder())
    {
        io::CFolder lFolder(lEntry.URI()); ///\todo fix this

        try
        {
            lNode->mExpandable=lFolder.HasChildren(); ///\todo HasAnyFolderChild();
        }
        catch(XPermission&)
        {
            lNode->mExpandable=false;
        }
    }
    Update();
    SetAutoUpdate(true);
    get_main_event_loop().GetMutex().unlock();

    Refresh();
}

void CDirTree::OnActionsThread()
{
    CDirTreeAction *lAction;
    while((lAction=mPendingActions.Get()))
    {
        switch(lAction->mCode)
        {
        case DIRTREE_FILLTREE:
            DoFillTree();
            break;
        case DIRTREE_EXPANDED:
            DoNodeExpandedFirstTime(lAction->mNode);
            break;
        case DIRTREE_SELECTDIR:
            DoSelectDir(lAction->mURI);
            break;
        }
    }
}

bool CDirTree::WillAccept(SNode *pNode,CDrop*)
{
    if(!pNode) return false;
    return true;
}

void CDirTree::OnDrop(SNode *pNode,CDrop *pDrop,EDNDActions pAction)
{
    if(!pNode) return;
    io::CURI lURI;
    get_toolkit()->GetClipboard(CClipboard::DRAGNDROP)->GetURI(lURI);
    msg_info() << "Dropped " << lURI << " on " << GetURI(pNode) << "\n";
    ///\todo CIOCopyJob *lJob=new CIOCopyJob(lURI,GetURI(pNode),CIOJob::IOJOB_SHOWPROGRESS);
    //lJob->Start();
}

void CDirTree::OnIntAction(TID pAction,const io::CFile &pFile)
{
///\todo     if(mInterface!=pInterface) return;
///    eIntAction(pAction,pInterface->GetFile()->GetURI());
}

/*namespace gui*/ } /*namespace buola*/ }
